package com.brayan.webapp.repository.general.i18n;

import java.util.LinkedList;
import java.util.List;

import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import org.apache.log4j.Logger;

import com.brayan.webapp.model.general.country.Country;
import com.brayan.webapp.model.general.country.Country_;
import com.brayan.webapp.model.general.i18n.Language;
import com.brayan.webapp.model.general.i18n.Language_;
import com.brayan.webapp.model.general.i18n.Locale;
import com.brayan.webapp.model.general.i18n.Locale_;
import com.brayan.webapp.model.general.i18n.Translation_;

@Stateless
public class LocaleRepository {

	private static Logger LOGGER = Logger.getLogger(LocaleRepository.class);

	@PersistenceContext(unitName = "ccms")
	EntityManager em;

	public Locale getLocaleByLanguageAndCountry(String languageCode, String countryCode) {

		if (countryCode == null || countryCode.isEmpty())
			return getLocaleByLanguage(languageCode);

		// Builder and query
		CriteriaBuilder critBuilder = em.getCriteriaBuilder();
		CriteriaQuery<Locale> criteriaQuery = critBuilder.createQuery(Locale.class);
		Root<Locale> rootLocale = criteriaQuery.from(Locale.class);
		Join<Locale, Language> language = rootLocale.join(Locale_.language);
		Join<Locale, Country> country = rootLocale.join(Locale_.country);

		// SELECT
		criteriaQuery.select(rootLocale);

		// WHERE
		criteriaQuery.where(critBuilder.equal(language.get(Language_.languageCode), languageCode),
				critBuilder.equal(country.get(Country_.countryCode), countryCode));

		TypedQuery<Locale> typedQuery = em.createQuery(criteriaQuery);
		Locale locale = null;
		try {
			locale = typedQuery.getSingleResult();
		} catch (NoResultException nrEx) {
			LOGGER.info("Did not find a locale by language code " + languageCode + " and country code " + countryCode);
		}
		return locale;
	}

	/**
	 * Return the Locale that has is dedicated to a language only! e.g. de-CH
	 * would not be retrieved by passing "de".
	 * 
	 * @param languageCode
	 * @return
	 */
	public Locale getLocaleByLanguage(String languageCode) {
		// Builder and query
		CriteriaBuilder critBuilder = em.getCriteriaBuilder();
		CriteriaQuery<Locale> criteriaQuery = critBuilder.createQuery(Locale.class);
		Root<Locale> rootLocale = criteriaQuery.from(Locale.class);
		Join<Locale, Language> language = rootLocale.join(Locale_.language, JoinType.INNER);

		// SELECT
		criteriaQuery.select(rootLocale);

		// WHERE
		criteriaQuery.where(critBuilder.equal(language.get(Language_.languageCode), languageCode),
				critBuilder.isNull(rootLocale.get(Locale_.country)));

		criteriaQuery.orderBy(critBuilder.asc(language.get(Language_.languageCode)));

		TypedQuery<Locale> typedQuery = em.createQuery(criteriaQuery);
		Locale locale = typedQuery.getSingleResult();
		return locale;
	}

	public Locale getLocaleByLocaleCode(String localeCode) {
		String languageCode = parseLanguageCodeFromLocaleCode(localeCode);
		String countryCode = parseCountryCodeFromLocaleCode(localeCode);
		return getLocaleByLanguageAndCountry(languageCode, countryCode);
	}

	public List<Locale> getAllLocalesExcept(String localeCode) {

		Locale locale = getLocaleByLocaleCode(localeCode);

		List<Locale> locales = new LinkedList<Locale>();
		CriteriaBuilder critBuilder = em.getCriteriaBuilder();
		CriteriaQuery<Locale> criteriaQuery = critBuilder.createQuery(Locale.class);

		Root<Locale> rootLocale = criteriaQuery.from(Locale.class);
		Join<Locale, Language> language = rootLocale.join(Locale_.language);
		
		criteriaQuery.select(rootLocale);

		Predicate finalPredicate = null, languagePredicate, countryPredicate;

		Expression<String> languageExpression = language.get(Language_.languageCode);

		languagePredicate = critBuilder.notEqual(languageExpression, locale.getLanguage().getLanguageCode());
		if (locale.getCountry() != null) {
			Join<Locale, Country> country = rootLocale.join(Locale_.country);
			Expression<String> countryExpression = country.get(Country_.countryCode);
			countryPredicate = critBuilder.notEqual(countryExpression, locale.getCountry().getCountryCode());
			finalPredicate = critBuilder.and(languagePredicate, countryPredicate);
		} else
			finalPredicate = critBuilder.and(languagePredicate);

		criteriaQuery.where(finalPredicate);

		TypedQuery<Locale> typedQuery = em.createQuery(criteriaQuery);
		locales = typedQuery.getResultList();
		return locales;
	}

	public List<Locale> getAllLocales() {
		List<Locale> locales = new LinkedList<Locale>();
		CriteriaBuilder critBuilder = em.getCriteriaBuilder();
		CriteriaQuery<Locale> criteriaQuery = critBuilder.createQuery(Locale.class);

		Root<Locale> rootLocale = criteriaQuery.from(Locale.class);
		criteriaQuery.select(rootLocale);

		TypedQuery<Locale> typedQuery = em.createQuery(criteriaQuery);
		locales = typedQuery.getResultList();
		return locales;
	}

	public void saveLocale(Locale locale) {
		em.persist(locale);
	}

	public void updateLocale(Locale locale) {
		em.merge(locale);
	}

	public void deleteLocale(Long id) {
		Locale locale = getLocaleById(id);
		if (locale != null)
			em.remove(locale);
	}

	public String parseLanguageCodeFromLocaleCode(String localeCode) {
		if (isValidLocaleCode(localeCode))
			return localeCode.substring(0, 2);
		else {
			LOGGER.error("Cannot parse language code from locale code (" + localeCode + ").");
			return null;
		}
	}

	public String parseCountryCodeFromLocaleCode(String localeCode) {
		if (isValidLocaleCode(localeCode) && localeCode.length() == 5)
			return localeCode.substring(3, 5);
		else {
			LOGGER.error("Cannot parse country code from locale code (" + localeCode + ").");
			return null;
		}
	}

	/**
	 * Tests if the local code e.g. de-CH or de, has the correct format and
	 * length.
	 * 
	 * @param localeCode
	 * @return
	 */
	public boolean isValidLocaleCode(String localeCode) {
		String regex = "(^[a-z]{2}$)|(^[a-z]{2}-[A-Z]{2}$)";
		return localeCode.matches(regex);
	}

	public Locale getLocaleById(long id) {
		return em.find(Locale.class, id);
	}

}
